#include "stdafx.h"
#include "Resource.h"
#include "SquarePane.h"
#include "SquareLine.h"
#include "Element.h"
#include "IElement.h"
#include "JElement.h"
#include "KElement.h"
#include "LElement.h"
#include "OElement.h"
#include "SElement.h"
#include "ZElement.h"
#include "Utility.h"
#include "Logger.h"

CSquarePane::CSquarePane(CWnd* pWnd, UINT nRowNum, UINT nColNum)
    : m_pPaneWnd(pWnd)
    , m_pElement(NULL)
    , m_nSquareRowNum(nRowNum)
    , m_nSquareColNum(nColNum)
    , m_nSquareWidth(0)
    , m_nScore(0)
{
    ASSERT(pWnd != NULL);

	// Pane rect
	m_pPaneWnd->GetClientRect(&m_rectPane);

    // Square width
    m_nSquareWidth = CalcSquareWidth(m_rectPane, m_nSquareRowNum, m_nSquareColNum);

    // Show rect
    UINT nHeightRemain = m_rectPane.Height() - m_nSquareWidth * m_nSquareRowNum;
    UINT nWidthRemain = m_rectPane.Width() - m_nSquareWidth * m_nSquareColNum;
	m_rectShow.left = m_rectPane.left + nWidthRemain / 2;
	m_rectShow.right = m_rectPane.right - nWidthRemain / 2;
	m_rectShow.top = m_rectPane.top + nHeightRemain / 2;
	m_rectShow.bottom = m_rectPane.bottom - nHeightRemain / 2;

	// DC
	m_pPaneDC = m_pPaneWnd->GetDC();
	
    // Square line index from bottom to top
	for (int i = 0; i < m_nSquareRowNum; i++)
	{
		CPoint point;
		point.x = m_rectShow.left;
		point.y = m_rectShow.bottom - (i+1) * m_nSquareWidth;

		m_vecSquareLine.push_back(CSquareLine(this, point, m_nSquareColNum, m_nSquareWidth));
	}

	// Initialize
	Init();
}

CSquarePane::~CSquarePane()
{
}

void CSquarePane::Init()
{
    for (SquareLineVecIter iter = m_vecSquareLine.begin(); iter != m_vecSquareLine.end(); ++iter)
    {
        iter->SetEmpty();
    }

    m_pntElementStart = GetSquare(19, 5).GetLeftTop();

    m_pPaneWnd->SetDlgItemInt(IDC_SCORE, m_nScore);
}

void CSquarePane::Draw()
{
    // Memeory DC
    CDC MemDC;
    CBitmap MemBitmap;

    MemDC.CreateCompatibleDC(NULL);
    MemBitmap.CreateCompatibleBitmap(m_pPaneDC, m_rectPane.Width(), m_rectPane.Height());

    CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap);


    // Draw background
    DrawBackGround(&MemDC);

    // Draw static squares
    for (SquareLineVecIter iter = m_vecSquareLine.begin(); iter != m_vecSquareLine.end(); ++iter)
    {
        iter->Fill(&MemDC);
    }

    // Draw element
    if (NULL != m_pElement)
    {
        m_pElement->Fill(&MemDC);
    }

    m_pPaneDC->BitBlt(0, 0, m_rectPane.Width(), m_rectPane.Height(), &MemDC, 0, 0, SRCCOPY);

    MemBitmap.DeleteObject();
    MemDC.DeleteDC();
}

void CSquarePane::MergeElement()
{
    Debug("Merge element begin");

	SquareList listSquare = m_pElement->GetSquareList();
	for (SquareListIter iter = listSquare.begin(); iter != listSquare.end(); ++iter)
	{
	    Position pos = ConvertPointToPosition(iter->GetLeftTop());
            
        CSquare& square = m_vecSquareLine[pos.nRow][pos.nCol];
        square.SetFlag(true);
        square.SetColor(iter->GetColor());
	}

	// Caculate score and update square line
	UpdateSquareLine();

    // Update score
    ::SetDlgItemInt(m_pPaneWnd->m_hWnd, IDC_SCORE, m_nScore, false);

    // Delete current element
    delete m_pElement;
    m_pElement = NULL;

    Debug("Merge element end");
}

bool CSquarePane::ValidateSquareList(const SquareList& listSquare)
{
    Debug("Validate square list begin");

	bool bResult = true;
	for (SquareListCIter citer = listSquare.begin(); citer != listSquare.end(); ++citer)
	{
		if (!SquareInPane(*citer) || SquareFlagged(*citer))
		{
			bResult = false;
			break;
		}
	}

    Debug("Validate square list end");

	return bResult;
}

bool CSquarePane::ValidateElement(const CElement* pElement)
{
    return ValidateSquareList(pElement->GetSquareList());
}

bool CSquarePane::SquareFlagged(const CSquare& square)
{
    if (!SquareInPane(square))
		return false;

    Position pos = ConvertPointToPosition(square.GetLeftTop());

	return m_vecSquareLine[pos.nRow][pos.nCol].GetFlag();
}

bool CSquarePane::SquareInPane(const CSquare &square)
{
	bool bResult = false;

	// Can replace by finding form map
	for (SquareLineVecIter iter = m_vecSquareLine.begin();
		 iter != m_vecSquareLine.end();
		 ++iter)
	{
		if (iter->SquareInLine(square))
		{
			bResult = true;
			break;
		}
	}

	return bResult;
}

void CSquarePane::DrawBackGround(CDC* pDC)
{
    CBrush brush, *pOldBrush;
	brush.CreateSolidBrush(RGB(0,0,0));
	pOldBrush = pDC->SelectObject(&brush);

	pDC->FillRect(&m_rectShow, &brush);
	pDC->SelectObject(pOldBrush);
}

CPoint CSquarePane::ConvertPositionToPoint(const Position& pos) const
{
    return CPoint(1,1);
}

Position CSquarePane::ConvertPointToPosition(const CPoint& pnt) const
{
    Position pos;
    
    pos.nRow = (m_rectShow.bottom - pnt.y) / m_nSquareWidth - 1;
    pos.nCol = (pnt.x - m_rectShow.left) / m_nSquareWidth;

    return pos;
}

void CSquarePane::UpdateSquareLine()
{
    // Add Score
	bool bScored = false;
    UINT nLineNum = 0;
	for (int i = 0; i < m_nSquareRowNum; i++)
	{
		CSquareLine& square_line = m_vecSquareLine[i];
		if (square_line.IsFull())
		{
			square_line.SetEmpty();

            nLineNum++;
			bScored = true;
		}
	}

	// Swap square line
	if (bScored)
	{
		// Find the first scored line
		int iFirstEmptyLine = -1;
		for (int i = 0; i < m_nSquareRowNum; i++)
		{
			if (m_vecSquareLine[i].IsEmpty())
			{
				iFirstEmptyLine = i;
				break;
			}
		}

		// Swap line by line
		if (iFirstEmptyLine != -1)
		{
			for (int j = iFirstEmptyLine + 1; j < m_nSquareRowNum; j++)
			{
				if (m_vecSquareLine[j].IsEmpty())
					continue;
				
				m_vecSquareLine[iFirstEmptyLine].SwapLineState(m_vecSquareLine[j]);

				iFirstEmptyLine++;
			}
		}
	}

    // Update score
    m_nScore += CalcScore(nLineNum);
}

void CSquarePane::AddElementAttr(UINT nCount)
{
    for (int i = 0; i < nCount; ++i)
    {
        ElementAttr attr;  
        attr.element_type = (ElementType)GetRandomNumber(0, 6);
        attr.element_state = (ElementState)GetRandomNumber(0, 3);

        m_queElementAttr.push(attr);
    }
}

CElement* CSquarePane::UpdateElement()
{
    Debug("Construct element");

    ASSERT(NULL == m_pElement);

    // Fill element attribute queue
    if (m_queElementAttr.empty())
    {
        AddElementAttr(2);
    }

    // Get next element attribute and pop out
    ElementAttr attr = m_queElementAttr.front();
    m_queElementAttr.pop();

    m_pElement = ConstructElement(m_pntElementStart, attr);
    
    ASSERT(NULL != m_pElement);

    return m_pElement;
}

CElement* CSquarePane::ConstructElement(const CPoint& pntStart, const ElementAttr& attr)
{
    Debug("Construct element");

    CElement *pElement = NULL;

	switch (attr.element_type)
	{
	case ET_I:
		{
			pElement = new CIElement(this, pntStart, m_nSquareWidth, attr.element_state, SM_CLOCK_WISE, RGB(255,0,0));
			break;
		}
	case ET_J:
		{
			pElement = new CJElement(this, pntStart, m_nSquareWidth, attr.element_state, SM_CLOCK_WISE, RGB(0,0,255));
			break;
		}
	case ET_K:
		{
			pElement = new CKElement(this, pntStart, m_nSquareWidth, attr.element_state, SM_CLOCK_WISE, RGB(51,255,51));
			break;
		}
	case ET_L:
		{
			pElement = new CLElement(this, pntStart, m_nSquareWidth, attr.element_state, SM_CLOCK_WISE, RGB(255,102,0));
			break;
		}
	case ET_O:
		{
			pElement = new COElement(this, pntStart, m_nSquareWidth, attr.element_state, SM_CLOCK_WISE, RGB(134,33,98));
			break;
		}
	case ET_S:
		{
			pElement = new CSElement(this, pntStart, m_nSquareWidth, attr.element_state, SM_CLOCK_WISE, RGB(13,98,23));
			break;
		}
	case ET_Z:
		{
			pElement = new CZElement(this, pntStart, m_nSquareWidth, attr.element_state, SM_CLOCK_WISE, RGB(0,255,204));
			break;
		}
	default:
		break;
	}
    
    ASSERT(NULL != pElement);

    return pElement;
}

ElementAttr CSquarePane::GetNextElementAttr()
{
    if (m_queElementAttr.empty())
    {
        AddElementAttr(1);
    }
        
    return m_queElementAttr.front();
}

CSquare CSquarePane::GetSquare(UINT nRow, UINT nCol) 
{ 
    return m_vecSquareLine[nRow][nCol];
}
CWnd* CSquarePane::GetPaneWnd() const 
{ 
    return m_pPaneWnd; 
}

UINT CSquarePane::GetSquareWidth() const 
{ 
    return m_nSquareWidth; 
}

UINT CSquarePane::GetSqaureRowNum() const 
{ 
    return m_nSquareRowNum; 
}

UINT CSquarePane::GetSquareColNum() const 
{ 
    return m_nSquareColNum; 
}

CRect CSquarePane::GetPaneRect() const 
{ 
    return m_rectPane; 
}

UINT CSquarePane::GetScore() const
{
    return m_nScore;
}

UINT CSquarePane::CalcScore(UINT nLineNum)
{
    UINT nScore = 0;

    if (1 == nLineNum)
    {
        nScore = 100;
    }
    else if (2 == nLineNum)
    {
        nScore = 300;
    }
    else if (3 == nLineNum)
    {
        nScore = 500;
    }
    else if (4 == nLineNum)
    {
        nScore = 800;
    }

    return nScore;
}

void CSquarePane::Restart()
{
    for (SquareLineVecIter iter = m_vecSquareLine.begin(); iter != m_vecSquareLine.end(); ++iter)
    {
        iter->SetEmpty();
    }
    
    m_nScore = 0;

    delete m_pElement;
    m_pElement = NULL;
}